package com.xunyin.netty;

import com.xunyin.cache.ManagerQRCodeCache;
import com.xunyin.cache.QRCodeCache;
import com.xunyin.constant.DeviceConst;
import com.xunyin.enums.*;
import com.xunyin.modules.device.service.mapper.TElectricEventTaskMapper;
import com.xunyin.modules.shareorder.service.mapper.TShareOrderMapper;
import com.xunyin.utils.DateUtils;
import com.xunyin.utils.StringUtils;
import com.xunyin.utils.TCPServerUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Date;

/**
 * TCP Server Handle
 *
 * @author jthong
 * @Version 1.0.0
 * @Date 2019/12/26 16:18
 */
@Component
public class NettyServerHandler extends SimpleChannelInboundHandler<Object> {
    private static Logger LOGGER = LoggerFactory.getLogger(NettyServerHandler.class);

    @Autowired
    TShareOrderMapper teaOrderMapper1;

    @Autowired
    TElectricEventTaskMapper stElectricEventTaskMapper1;

    private static TShareOrderMapper teaOrderMapper;

    private static TElectricEventTaskMapper stElectricEventTaskMapper;

    @PostConstruct
    public void init() {
        teaOrderMapper = teaOrderMapper1;
        stElectricEventTaskMapper = stElectricEventTaskMapper1;
    }

    /**
     * 服务端处理客户端websocket请求的核心方法，这里接收了客户端发来的信息
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object info) throws Exception {
        ByteBuf buf = (ByteBuf) info;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String cmd = TCPServerUtil.bytesToHexString(req, req.length);
        if (StringUtils.isEmpty(cmd)) {
            return;
        }
        LOGGER.info("TCP Server 收到消息：{}", cmd);
        // 处理收到的指令
        handleCmd(cmd, ctx);
    }

    /**
     * 指令处理逻辑
     * 约定规则：
     * Mac地址长度12位
     *
     * @param cmd
     * @param ctx
     */
    private void handleCmd(String cmd, ChannelHandlerContext ctx) throws Exception {
        // 心跳指令
        if (cmd.startsWith(CommandPrefixEnum.HEARTBEAT.getName())) {
            String id = TCPServerUtil.getId(ctx);
            EntityCacheHelp.channelIdCache.put(id, ctx);
            String mac = cmd.replaceFirst(CommandPrefixEnum.HEARTBEAT.getName(), "");
            EntityCacheHelp.channelMacCache.put(mac, ctx);
            EntityCacheHelp.channelMacToIdCache.put(mac, id);
//            LOGGER.info("NettyServerHandler: RemoteAddress {} active! heart beat:{}", id, cmd);
        } else {
            cmd = TCPServerUtil.hexStringToString(cmd);

            // 解析真实的二维码指令
            String realCmd = "";
            if (cmd.startsWith(CommandPrefixEnum.ROOM_QR_CODE.getName())) {
                LOGGER.info("DOOR_QR_CODE ： {}", cmd);
                realCmd = cmd.substring(DeviceConst.QR_CODE_PREFIX_LENGTH);
            }

            LOGGER.info("realCmd ： {}", realCmd);
            if (StringUtils.isEmpty(realCmd)) {
                return;
            }

            if (realCmd.length() < 12) {
                return;
            }

            Integer qrCodeType = 0;
            if (QRCodeCache.checkQRCode(realCmd.substring(2, realCmd.length()))) {
                qrCodeType = 1;
            } else if (ManagerQRCodeCache.checkQRCode(realCmd.substring(2, realCmd.length()))) {
                qrCodeType = 2;
            }

            String deviceNo = realCmd.substring(realCmd.length() - 12, realCmd.length() - 1) + "1";
            String qrCodeNo = realCmd.substring(0, DeviceConst.QR_CODE_NO_CMD_SIZE);
            String realDeviceNo = realCmd.substring(realCmd.length() - 12, realCmd.length() - 1) + qrCodeNo.substring(1, DeviceConst.QR_CODE_NO_CMD_SIZE);
            String authDeviceAddr = "";
            if (qrCodeType == 1) {
                authDeviceAddr = realCmd.substring(realCmd.length() - DeviceConst.AUTH_DEVICE_ADDR_SIZE, realCmd.length());
                authDeviceAddr = authDeviceAddr.replaceAll("A", "0");
                // TODO
                if (!(qrCodeNo.equals("04") || authDeviceAddr.equals(qrCodeNo))) {
                    return;
                }

                if (qrCodeNo.equals("04")) {
                    authDeviceAddr = qrCodeNo;
                }
            } else if (qrCodeType == 2) {
                authDeviceAddr = qrCodeNo;
                LOGGER.info("=================NettyServerhandler:qrCodeType=2:deviceNo={}", deviceNo);
                //如果存在正常订单，则不允许开门
                if (teaOrderMapper.judgeHasOrder(deviceNo) > 0) {
                    LOGGER.info("存在正常订单，不允许开门");
                    return;
                }
            }

            ChannelHandlerContext sendCtx = EntityCacheHelp.channelMacCache.getIfPresent(deviceNo);
            //门禁控制板用地址1
            String openDoorCmd = TCPServerUtil.generateCommand(CtrDeviceAddrEnum.ADDR_NUM_1.getName(), CtrDeviceChannelEnum.getObjectByName(authDeviceAddr), CommandCodeEnum.OPEN);
            LOGGER.info("================================================openDoorCmd={}", openDoorCmd);
            byte[] bytes = TCPServerUtil.hexStringToByteArray(openDoorCmd);
            ByteBuf buf = Unpooled.wrappedBuffer(bytes);
            sendCtx.writeAndFlush(buf);

            //开始通电
            if (qrCodeType == 2) {
                turnOnElectric(realDeviceNo);
            }
        }
    }

    private void turnOnElectric(String deviceNo) throws Exception {
        //再次过滤，避免重新通电及重新计时
        if (ManagerQRCodeCache.checkElectricStatusIsOn(deviceNo)) {
            LOGGER.info("已通电:deviceNo={},不再重复通电及计时。", deviceNo);
            return;
        }
        ChannelHandlerContext sendCtx = EntityCacheHelp.channelMacCache.getIfPresent(deviceNo);
        if (sendCtx == null) {
            LOGGER.error("设备未上线:deviceNo={}", deviceNo);
            throw new Exception();
        }

/*        StElectricEventTask record = new StElectricEventTask();
        record.setDeviceNo(deviceNo);
        record.setRelayThreeStatus(2);
        record.setRelayTwoStatus(2);
        record.setTaskStatus(1);
        record.setTaskTime(new Date());
        record.setTaskType(2);
        stElectricEventTaskMapper.insert(record);*/

        LOGGER.info("第一次通电");
        Thread.sleep(2500);
        byte[] bytes = TCPServerUtil.hexStringToByteArray(RelayOnCmdEnum.parse(2).getName());
        ByteBuf buf = Unpooled.wrappedBuffer(bytes);
        sendCtx.writeAndFlush(buf);

        Thread.sleep(2500);
        bytes = TCPServerUtil.hexStringToByteArray(RelayOnCmdEnum.parse(3).getName());
        buf = Unpooled.wrappedBuffer(bytes);
        sendCtx.writeAndFlush(buf);

        LOGGER.info("通电指令发送完成");
        ManagerQRCodeCache.setElectricStatusOnCache(deviceNo);
        //更新时间，重新计时（重复扫码不会重复计时）
        ManagerQRCodeCache.setQRCodeCache(deviceNo, new Date());
    }

    /**
     * 根据deviceNo发送指令到指定设备
     *
     * @param deviceNo
     * @param ctrDeviceAddr
     * @param ctrDeviceChannelEnum
     * @param commandCodeEnum
     */
    public static void sendCmdBydeviceNo(String deviceNo, String ctrDeviceAddr, CtrDeviceChannelEnum ctrDeviceChannelEnum, CommandCodeEnum commandCodeEnum) {
        ChannelHandlerContext sendCtx = EntityCacheHelp.channelMacCache.getIfPresent(deviceNo);
        String openDoorCmd = TCPServerUtil.generateCommand(ctrDeviceAddr, ctrDeviceChannelEnum, CommandCodeEnum.OPEN);
        LOGGER.info("sendCmdBydeviceNo.openDoorCmd:{}", openDoorCmd);
        byte[] bytes = TCPServerUtil.hexStringToByteArray(openDoorCmd);
        ByteBuf buf = Unpooled.wrappedBuffer(bytes);
        sendCtx.writeAndFlush(buf);
    }

    /**
     * 客户端主动连接服务端
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        String id = TCPServerUtil.getId(ctx);
        LOGGER.info("NettyServerHandler: RemoteAddress【{}】 active!", id);
        EntityCacheHelp.channelIdCache.put(id, ctx);
        super.channelActive(ctx);
    }

    /**
     * 异常处理
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        LOGGER.error("连接异常,连接异常：" + DateUtils.parseDate2String(new Date(), "yyyy-MM-dd HH:mm:ss") + cause.getMessage(), cause);
        ctx.fireExceptionCaught(cause);
        removeChannelMap(ctx);
        ctx.close();
    }

    /**
     * 删除map中ChannelHandlerContext
     */
    private void removeChannelMap(ChannelHandlerContext ctx) {
        for (Object key : NettyServer.map.keySet()) {
            if (NettyServer.map.get(key) != null && NettyServer.map.get(key).equals(ctx)) {
                NettyServer.map.remove(key);
            }
        }
    }

    /**
     * 读取完毕客户端发送过来的数据之后的操作
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        LOGGER.info("服务端接收数据完毕..");
        ctx.channel().write("service receive complete then send to customer"); //向客户端发送一条信息
        ctx.channel().flush();
    }
    //******************************************************************************************************************


    /**
     * 客户端主动断开服务端的链接,关闭流
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(ctx.channel().localAddress().toString() + " 通道异常！");
        removeChannelMap(ctx);
        ctx.close(); // 关闭流
    }

    /**
     * 进行心跳检测, 保证用户在线
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent stateEvent = (IdleStateEvent) evt;
            PingWebSocketFrame ping = new PingWebSocketFrame();
            switch (stateEvent.state()) {
                case READER_IDLE: //读空闲 服务器端
                    LOGGER.info("{[" + ctx.channel().remoteAddress() + "]--->(服务端 read 空闲)}");
                    ctx.writeAndFlush(ping);
                    break;
                case WRITER_IDLE: //写空闲 服务器端
                    LOGGER.info("{[" + ctx.channel().remoteAddress() + "]--->(服务端 write 空闲)}");
                    ctx.writeAndFlush(ping);
                    break;
                case ALL_IDLE: //读写空闲 服务器端
                    LOGGER.info("{[" + ctx.channel().remoteAddress() + "]--->(服务端 读写 空闲)}");
            }
        }
    }

    /**
     * 返回应答给客户端
     */
    private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse res) {
        if (res.getStatus().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
        }

        // 如果是非 keep-alive , 关闭连接
        ChannelFuture cf = ctx.channel().writeAndFlush(res);
        if (!HttpHeaders.isKeepAlive(req) || res.getStatus().code() != 200) {
            cf.addListener(ChannelFutureListener.CLOSE);
        }
    }

    /**
     * 断开连接
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        LOGGER.info("handlerRemoved ---->" + ctx.channel());
    }


    /**
     * 读取客户端发来的数据
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        String[] data = msg.toString().split("id=");
        if (data != null && data.length > 1) {
            String[] data1 = data[1].split(";");
            String id = data1[0];
            if (NettyServer.map.get(id) != null && NettyServer.map.get(id).equals(ctx)) { //不是第一次连接
                LOGGER.info("接收数据成功！" + DateUtils.parseDate2String(new Date(), "yyyy-MM-dd HH:mm:ss"));
            } else { //如果map中没有此ctx 将连接存入map中
                NettyServer.map.put(id, ctx);
                LOGGER.info("连接成功，加入map管理连接！" + "mn:" + id + " : " + ctx + "" + DateUtils.parseDate2String(new Date(), "yyyy-MM-dd HH:mm:ss"));
            }
        } else {
            LOGGER.info("不是监测数据" + msg.toString() + " : " + DateUtils.parseDate2String(new Date(), "yyyy-MM-dd HH:mm:ss"));
        }
        ctx.writeAndFlush("Received your message : " + msg.toString());
    }
}